/*
 * Copyright Alex Wilkinson
 * Released under New BSD Licence
 */
// Constants for convience
if (typeof(Cc) == "undefined")
  var Cc = Components.classes;
if (typeof(Ci) == "undefined")
  var Ci = Components.interfaces;
if (typeof(Cu) == "undefined")
  var Cu = Components.utils;
if (typeof(Cr) == "undefined")
  var Cr = Components.results;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");

var DEBUG = true; // non-const *only* so tweakable in server tests
function dumpn(str)
{
  if (DEBUG)
    dump(str + "\n");
}

// work out what interfaces we support
if (Ci.IBFDNSSDRegisterListener == undefined)
	daapd_supported_interfaces = [Ci.nsIObserver, Ci.awIDAAPD,  Ci.awIHttpRequestHandler, Ci.nsISupportsWeakReference]
else
	daapd_supported_interfaces = [Ci.nsIObserver, Ci.awIDAAPD,  Ci.awIHttpRequestHandler, Ci.nsISupportsWeakReference, Ci.IBFDNSSDRegisterListener]

function daapd() {
	Cu.import("resource://telescope/modules/HTTPUtils.js"); 
	Cu.import("resource://telescope/modules/Preferences.js"); 
	Cu.import("resource://telescope/modules/webtoolkit.base64.js"); 
	Cu.import("resource://telescope/modules/md5.js"); 
	Cu.import("resource://telescope/modules/DMAP.js"); 

    Cu.import("resource://app/jsmodules/sbProperties.jsm");
    Cu.import("resource://app/jsmodules/sbLibraryUtils.jsm");
   
    DMAP._contentCodes = this._contentCodes;

};

daapd.prototype = {  
  /**
   * XPCOM Setup
   */
  classDescription: "Provides a DAAP server via the telescope http component",
  classID:          Components.ID("{5bd6d440-fc81-11de-8a39-0800200c9a66}"),  // unique CID
  contractID:       "@awilco.net/daapd;1",             // "@mozilla.org/example;1

  /**
   * The xpcom factory for this component, if not included then the default
   * will be used:
   *  new className()).QueryInterface(iid)
  //_xpcom_factory : { },
 
  /**
   * The Catagorys for this component, this can be used to search for all
   * components in the same catagory. It also defines if this is an instance
   * or a service. These are optional so you can comment out if you don't need
   * it, this will default to an instance in that case.
   */
  _xpcom_categories: [{
    category: "app-startup",  // Catagory for this component
    entry: "telescope.daapd",        // Catagory entry name
    service: true                    // Set to true to make this a service
  }],

   BinaryOutputStream: Components.Constructor("@mozilla.org/binaryoutputstream;1",
                             "nsIBinaryOutputStream",
                             "setOutputStream"),
  _ioService: Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService),
  dmapIDProperty : "http://awilco.net/data/1.0#DMAPID",
  _authRequired: false,
  _password: '',
  _serverName: 'Songbird Music Server',
  _ID: '',
  _DbID: '',
  _dnssdService: null,
  _daapDnssdInstance: null,
  _dacpDnssdInstance: null,
  _server: null,
  _port: 3689,  
  _knownRemotes: [],
  _currentSessions: [],
  _currentDBRevision: 1,
  _currentPlayerRevision: 1,
  _lastDmapID: 0,
    observe: function(aSubject, aTopic, aData) 
    {
		switch(aTopic) 
		{
		  case "app-startup":
				dumpn('Loading daap deamon');
            var obsService = Cc["@mozilla.org/observer-service;1"]
                           .getService(Ci.nsIObserverService);
            obsService.addObserver(this,'profile-after-change',false);
            break;
          case "profile-after-change":
		  
			//Register our property
			var pMgr = Cc["@songbirdnest.com/Songbird/Properties/PropertyManager;1"]
				   .getService(Ci.sbIPropertyManager);
			if (!pMgr.hasProperty(this.dmapIDProperty)) 
			{
				var pI = Cc["@songbirdnest.com/Songbird/Properties/Info/Text;1"]
						.createInstance(Ci.sbITextPropertyInfo);
				pI.id = this.dmapIDProperty;
				pI.displayName = "DMAP ID"
				pI.userEditable = false;
				pI.userViewable = false;
				pMgr.addPropertyInfo(pI);
		   } else {
			 dumpn('Somehow DMAP ID property is already set... this is bad');
		   }
		   
   
			//Load preferences
		    this._serverName = Preferences.get('extensions.wings.serverName');
		    this._port = Preferences.get('extensions.wings.port');
			this._lastDmapID = parseInt(Preferences.get('extensions.telescope.dmapid','0'));
			this._ID = Preferences.get('extensions.telescope.ID','');
			
			var knownRemotes = Preferences.get('extensions.telescope.knownRemotes');
			this._knownRemotes = knownRemotes.split(';');
			
			//create a server ID if it does not exist
			if (this._ID == '')		{
				this._ID = this.generateUniqueHex(16);
				this._ID = this._ID.toUpperCase();
				Preferences.set('extensions.telescope.ID',this._ID);
			}
			if (this._DbID == '')	{
				this._DbID = this.generateUniqueHex(16);
				this._DbID = this._DbID.toUpperCase();
				Preferences.set('extensions.telescope.DbID',this._DbID);
			}
			
			this.server.registerPathHandler('/',this);
			
			//Create a restart-server loop
			//this._respawnTimer = Components.classes["@mozilla.org/timer;1"].createInstance(Components.interfaces.nsITimer);
			//this._respawnTimer.initWithCallback({notify: this.callInContext(this.checkServerIsRunning)}, 5000, Components.interfaces.nsITimer.TYPE_REPEATING_SLACK);
			
			//try to connect to Bonjour Proxy
			try 
			{
                dumpn('Attempting bonjour announcement');
				this._dnssdService = Components.classes["@bonjourfoxy.net/BFDNSSDService;1"]
                                 .createInstance(Components.interfaces.IBFDNSSDService);
				
				var kvPairTxtvers = Cc["@mozilla.org/variant;1"].createInstance(Ci.nsIWritableVariant);
				var kvPairiTSh = Cc["@mozilla.org/variant;1"].createInstance(Ci.nsIWritableVariant);
				var kvPairMID = Cc["@mozilla.org/variant;1"].createInstance(Ci.nsIWritableVariant);
				var kvPairDatabase = Cc["@mozilla.org/variant;1"].createInstance(Ci.nsIWritableVariant);
				var kvPairdmv = Cc["@mozilla.org/variant;1"].createInstance(Ci.nsIWritableVariant);
				var kvPairVersion = Cc["@mozilla.org/variant;1"].createInstance(Ci.nsIWritableVariant);
				var kvPairOSsi = Cc["@mozilla.org/variant;1"].createInstance(Ci.nsIWritableVariant);
				var kvPairMachine = Cc["@mozilla.org/variant;1"].createInstance(Ci.nsIWritableVariant);
				var kvPairMedia = Cc["@mozilla.org/variant;1"].createInstance(Ci.nsIWritableVariant);
				var kvPairMachineID = Cc["@mozilla.org/variant;1"].createInstance(Ci.nsIWritableVariant);
				var kvPairPassword = Cc["@mozilla.org/variant;1"].createInstance(Ci.nsIWritableVariant);
				var kvPairs = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
				
				kvPairTxtvers.setFromVariant("txtvers=1");
				kvPairiTSh.setFromVariant("iTSh Version=196609");
				kvPairMID.setFromVariant("MID=0x468EC69244599825");
				kvPairDatabase.setFromVariant("Database ID=9031099074C15E05");
				kvPairdmv.setFromVariant("dmv=131077");
				kvPairVersion.setFromVariant("Version=196616");
				kvPairOSsi.setFromVariant("OSsi=0x10313");
				kvPairMachine.setFromVariant("Machine Name=" + this._serverName);
				kvPairMedia.setFromVariant("Media Kinds Shared=5");
				kvPairMachineID.setFromVariant("Machine ID=CBDABF19F8E9");
				kvPairPassword.setFromVariant("Password=" + ((this._authRequired) ? '1' : '0'));
				kvPairs.appendElement(kvPairTxtvers, 0);
				kvPairs.appendElement(kvPairiTSh, 0);
				kvPairs.appendElement(kvPairMID, 0);
				kvPairs.appendElement(kvPairDatabase, 0);
				kvPairs.appendElement(kvPairdmv, 0);
				kvPairs.appendElement(kvPairVersion, 0);
				kvPairs.appendElement(kvPairOSsi, 0);
				kvPairs.appendElement(kvPairMachine, 0);
				kvPairs.appendElement(kvPairMedia, 0);
				kvPairs.appendElement(kvPairMachineID, 0);
				kvPairs.appendElement(kvPairPassword, 0);
		
				this._daapDnssdInstance = this._dnssdService.register(0,this._serverName,'_daap._tcp',"","",this._port,kvPairs,this);
				
				//register touch-able service
				//this._dacpDnssdInstance = this._dnssdService.register(0,this._ID,'_touch-able._tcp',"","",this._port,"Ver","1000",this);
				//this._dacpDnssdInstance2 = this._dnssdService.register(0,this._ID,'_touch-able._tcp',"","",this._port,"DvSv","1905",this);
				//this._dacpDnssdInstance3 = this._dnssdService.register(0,this._ID,'_touch-able._tcp',"","",this._port,"DbId",this._ID,this);
				//this._dacpDnssdInstance4 = this._dnssdService.register(0,this._ID,'_touch-able._tcp',"","",this._port,"DvTy","iTunes",this);
				//this._dacpDnssdInstance5 = this._dnssdService.register(0,this._ID,'_touch-able._tcp',"","",this._port,"OSsi","0xD97F",this);
				var kvTxtvers = Cc["@mozilla.org/variant;1"].createInstance(Ci.nsIWritableVariant);
				var kvVer = Cc["@mozilla.org/variant;1"].createInstance(Ci.nsIWritableVariant);
				var kvDvSv = Cc["@mozilla.org/variant;1"].createInstance(Ci.nsIWritableVariant);
				var kvDbID = Cc["@mozilla.org/variant;1"].createInstance(Ci.nsIWritableVariant);
				var kvDvTy = Cc["@mozilla.org/variant;1"].createInstance(Ci.nsIWritableVariant);
				var kvOSsi = Cc["@mozilla.org/variant;1"].createInstance(Ci.nsIWritableVariant);
				var kvCtln = Cc["@mozilla.org/variant;1"].createInstance(Ci.nsIWritableVariant);
				var kvPairsDACP = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
				
				kvTxtvers.setFromVariant("txtvers=1");
				kvPairOSsi.setFromVariant("OSsi=0x10313");
				kvCtln.setFromVariant("CtlN=" + this._serverName);
				kvVer.setFromVariant("Ver=131073");
				kvDvSv.setFromVariant("DvSv=2305");
				kvDvTy.setFromVariant("DvTy=iTunes");
				kvDbID.setFromVariant("DbId=" + this._DbID);
				kvPairsDACP.appendElement(kvTxtvers, 0);
				kvPairsDACP.appendElement(kvPairOSsi, 0);
				kvPairsDACP.appendElement(kvCtln, 0);
				kvPairsDACP.appendElement(kvVer, 0);
				kvPairsDACP.appendElement(kvDvSv, 0);
				kvPairsDACP.appendElement(kvDvTy, 0);
				kvPairsDACP.appendElement(kvDbID, 0);
				
				this._dacpDnssdInstance6 = this._dnssdService.register(0,this._ID,'_touch-able._tcp',"","",this._port,kvPairsDACP,this);
				
                dumpn('Bonjour announcement successful');
				
			} catch (e)
			{
				dumpn('Error registering with Bonjour: ' + e);
			}
			
			//Listen for media core changes
			this._mediaCoreListener.that = this;
			var gMM = Cc["@songbirdnest.com/Songbird/Mediacore/Manager;1"].getService(Ci.sbIMediacoreManager); 
			gMM.addListener(this._mediaCoreListener);
						
			break;
	    }
  },
    /**
   * Handles DAAP requests, use path as opposed to metadata.path, as this is local to DAAP server root
   * path is an array of path components
   */
    handle: function(metadata,response)
    {
		var path = metadata.path.substring(1).split('/');
		var params = HTTPUtils.decodeURIParameters(metadata.queryString);
		
		var comp = path.shift().toLowerCase();
		
		switch(comp)
		{
			case '':
			case 'server-info':
				return this.serveServerInfo(metadata,response);
			case 'content-codes':
				return this.serveContentCodes(metadata,response);
			case 'login':
				return this.serveLogin(metadata,params,response);	
		}
		
		switch (comp)
		{
			case 'update':
				return this.handleUpdate(metadata,response);
			case 'db':
			case 'databases':
				return this.handleDatabase(path,params,metadata,response);
			case 'ctrl-int':
				return this.handlePlayer(path,params,metadata,response);
		}
		
		//throw HTTPUtils.HTTP_401;
	},
	handleDatabase: function(path,params,metadata,response)
	{
		if (path.length > 1)
			var dbid = path.shift().toLowerCase();
		else
			return this.serveDatabases(LibraryUtils.mainLibrary.createView(),metadata,response);
		
		var view = LibraryUtils.mainLibrary.createView();
		view.filterConstraint = LibraryUtils.createConstraint([[ [SBProperties.hidden, ["0"]] ] ]); 
		this.handleView(view,path,params,metadata,response);
	},
	handleView: function(view,path,params,metadata,response)
	{
		var comp = path.shift().toLowerCase();
		switch(comp)
		{
			case 'items':
				return this.handleItems(view,path,params,metadata,response);
				break;
			case 'c':
			case 'containers':
				if (path.length > 1)
				{
					var containerID = path.shift().toLowerCase();
					
					if (containerID == '1' || containerID == '2')
					{
						//this is a referral back to the whole library database
						return this.handleView(view,path,params,metadata,response);
					}
					view.filterConstraint = LibraryUtils.createConstraint([[ [SBProperties.isList, ["1"]] ],[ [this.dmapIDProperty, [containerID]] ]]);
				
					if (view.length < 1)
						throw HTTPUtils.HTTP_404;
					
					newView = view.getItemByIndex(0).createView();
					return this.handleView(newView,path,params,metadata,response);
				}
				else
					return this.serveContainersFromView(view,params,metadata,response);
				break;
			case 'browse':
				return this.handleViewBrowse(view,path,params,metadata,response);
			case 'groups':
				if (params['group-type'] != undefined)
				{
					if (params['group-type'] == 'albums')
						return this.handleGroupsFromView(SBProperties.albumName,view,path,params,metadata,response);
					else if (params['group-type'] == 'artists')
						return this.handleGroupsFromView(SBProperties.artistName,view,path,params,metadata,response);
					else if (params['group-type'] == 'genres')
						return this.handleGroupsFromView(SBProperties.genre,view,path,params,metadata,response);
					else if (params['group-type'] == 'composers')
						return this.handleGroupsFromView(SBProperties.composerName,view,path,params,metadata,response);
				}
				throw HTTPUtils.HTTP_400;
		}
	},
	handleItems: function(view,path,params,metadata,response)
	{
		if (path.length < 1)
			//serve a list of items
			return this.serveItemsFromView(view,params,metadata,response);
		else
		{
			//get an actual item
			//final section of path is number.extension
			//extract id number (index in the library non-hidden view)
			var name  = path.shift();
			
			var iPeriod = name.indexOf('.');
			var dmapIDStr = (iPeriod == -1) ? name : name.substr(0,iPeriod);
			
			var view = LibraryUtils.mainLibrary.createView();
			//filter DmapID
			view.filterConstraint = LibraryUtils.createConstraint([[ [this.dmapIDProperty, [dmapIDStr]] ]]); 
	
			if (view.length < 1)
				throw HTTPUtils.HTTP_404;
				
			var item = view.getItemByIndex(0);
			return this.handleMediaItem(item,path,params,metadata,response);
		}
	},
	handleViewBrowse: function(view,path,params,metadata,response)
	{
		if (path.length < 1) throw HTTPUtils.HTTP_400;
		var comp = path.shift().toLowerCase();
		switch(comp)
		{
			case 'artists':
				return this.servePropertyBrowseFromView(SBProperties.artistName,view,params,metadata,response);
			case 'albums':
				return this.servePropertyBrowseFromView(SBProperties.albumName,view,params,metadata,response);
			case 'genres':
				return this.servePropertyBrowseFromView(SBProperties.genre,view,params,metadata,response);
			case 'composers':
				return this.servePropertyBrowseFromView(SBProperties.composerName,view,params,metadata,response);
			default:
				throw HTTPUtils.HTTP_400;
		}
	},
	serveServerInfo: function(metadata, response)
	{
		var dateObj = new Date();
		var time = dateObj.getTime();
		var offset = -1*dateObj.getTimezoneOffset()*60;
		r = { msrv: {
				mstt: 200,
				mpro: '0205',
				apro: '0308',
				aeSV: '0301',
				aeFP: true,
				ated: 3,
				msed: true,
				//msml: { msma: [37531829544448,129373111067648] },
				ceWM: 0,
				ceVO: 0,
				minm: this._serverName,
				mslr: this._authRequired,
				mstm: 1800,
				msal: true,
				msas: 3,
				msup: true,
				mspi: true,
				msex: true,
				msbr: true,
				msqy: true,
				msix: true,
				msrs: true,
				msdc: 1,
				mstc: time,
				msto: offset,
				}
			};
		this.sendResponse(response,r);
	},
	serveContentCodes: function(metadata, response)
	{
		r = { mccr: {
				mstt: 200,
				mdcl: { mlit: [] },
				}
			};
		for (var index in this._contentCodes)
		{
			r.mdcl.mlit.push({
				mcna: this._contentCodes[index].name,
				mcnm: DMAP.codeToInt(index),
				ncnm: DMAP.typeToShort(this._contentCodes[index].type),
				});
		}
		
		this.sendResponse(response,r);
	},
	/*
	* Checks for either a pairing code, if we are a remote, or for a HTTP password if required for DAAP Access
	*/
	serveLogin: function(metadata,params,response)
	{
		//2 methods, either they give us a known Code (for remotes)
		// or they send us a password in the HTTP headers
		
		var isRemote = false;
		if (params['pairing-guid'] == undefined)
		{
			
			if (this._authRequired)
			{
				if (!this.checkHTTPHeaderAuthorization(metadata))
				{
					this.serveAuthorizationRequest(metadata,response);
					return;
				}
			}
			isRemote = false;
		}
		else
		{
			isKnown = false;
			dumpn('pairing-guid: ' + params['pairing-guid']);
			for(index in this._knownRemotes)
			{
				if (this._knownRemotes[index] == params['pairing-guid'])
					isKnown = true;
			}
			//FIXME: Hack while Tunesremote cannot do pairing
			//This means anyone can access our server!
			if (params['pairing-guid'] == '0x')
				isKnown = true;
				
			if (!isKnown)
				throw HTTPUtils.HTTP_403;		//Forbidden
			
			isRemote = true;
		}
		
		//Create a new session id
		var newSessionID = -1;
		alreadyExists = false;
		do
		{
			newSessionID = Math.floor(Math.random() * /*4294967296*/ 2147483648);
			
			//Check for collision
			alreadyExists = false;
			for(sessionID in this._currentSessions)
				if (newSessionID == sessionID.value) alreadyExists = true;
		}
		while(alreadyExists);
		
		this._currentSessions.unshift({ value: newSessionID, 'isRemote': isRemote});
		
		r = { mlog: {
				mstt: 200,
				mlid: newSessionID,
				}
			};
		
		this.sendResponse(response,r);	
	},
	/*
	 * sends current server revision
	 */
	handleUpdate: function(metadata,response)
	{
		r = { mupd: {
				mstt: 200,
				msur: this._currentDBRevision,
				}
			};
		
		this.sendResponse(response,r);		
	},
	/* 
	 * serves the songbird database as the only database available
	*/
	serveDatabases: function(view,metadata,response)
	{
		//get number of songs
		var propIndex = view.cascadeFilterSet.appendFilter(SBProperties.isList);
		view.cascadeFilterSet.set(propIndex, ['0'], 1);
		var songCount = view.length;
		
		view.cascadeFilterSet.set(propIndex, ['1'], 1);
		var containerCount = view.length;
		
		var mediaList = view
		r = { avdb: {
				mstt: 200,
				muty: 0,
				mtco: 1,
				mrco: 1,
				mlcl: {
					mlit: [{
						miid: 1,
						mper: 1,
						minm: 'Songbird Database',
						mimc: songCount,
						mctc: containerCount,
						}]
					},
				}
			};
			
		this.sendResponse(response,r);		
	},
	serveItemsFromView: function(view,params,metadata,response)
	{
		//ignore items
		view.filterConstraint = LibraryUtils.createConstraint([[ [SBProperties.isList, ["0"]] ],[ [SBProperties.hidden, ["0"]] ]]);
		
		//apply filters
		dumpn('Query: ' + params.query);
		this.applyItemsFilters(view,params);
		
		var start = 0;
		var end = Number.MAX_VALUE;
		if (typeof(params.index) != 'undefined')
		{
			var iDelimiter = params['index'].indexOf('-');
			start = parseInt(params['index'].substr(0,iDelimiter));
			end = parseInt(params['index'].substr(iDelimiter+1));
		}
		var length = view.length;
		var count = (end >= length) ? length-start : end-start+1;
		//list of request metadata
		var metaStr = (typeof(params['meta']) == 'undefined') ? '' : params['meta'];
		var metaNameList = metaStr.split(',');
		
		r = { apso: {
				mstt: 200,
				muty: 0,
				mtco: length,
				mrco: count,
				mlcl: { mlit: new Array(count) },
				},
			};
		
		dump('Starting listing \n');
		var trackName;
		var albumName;
		var artistName;
		var duration;
		var genre;
		var contentLength;
		var contentType;
		var dmapID;
		var j=0;
		for (var i=start;i<view.length;i++)
		{
			
			if (i>end)
				break;	//stop after requested values
				
			curMediaItem = view.getItemByIndex(i);
			r.apso.mlcl.mlit[j] = this.getItemInfo(curMediaItem,metaNameList);
			j++;
		}
		dump('Done enumerating view, sending response \n');
		
		this.sendResponse(response,r);
		dump('Response Sent\n');
	},
	serveContainersFromView: function(view,params,metadata,response)
	{
		//ignore items
		view.filterConstraint = LibraryUtils.createConstraint([[ [SBProperties.isList, ["1"]] ],[ [SBProperties.hidden, ["0"]] ]]);
		
		//Apply filters
		//TODO: Currently we can use the same filter code as for items (as searches search all fields)
		//In Future we may need a separate function here
		this.applyItemsFilters(view,params);

		var start = 0;
		var end = Number.MAX_VALUE;
		if (typeof(params['index']) != 'undefined')
		{
			var iDelimiter = params['index'].indexOf('-');
			start = params['index'].substr(0,iDelimiter);
			end = params['index'].substr(iDelimiter+1);
		}
		
		//list of request metadata
		var metaStr = (typeof(params['meta']) == 'undefined') ? '' : params['meta'];
		var metaNameList = metaStr.split(',');
		
		var count = view.length;
		var curMediaList;
		var curDaapID;
		r = { aply: {
				mstt: 200,
				muty: 0,
				mtco: count,
				mrco: (end >= count) ? count-start : end-start+1,
				mlcl: { mlit: new Array(count) },
				},
			};
		
		var j=0;
		for (var i=start;i<count;i++)
		{				
			if (i>end)
				break;	//stop after requested values
			

			//HACK: GAH! for some reason itunes sends a 'Server' container, and its whole music database as 'Music' playlist, so we need to check for this
			if (i==0 && view.mediaList != null && view.mediaList.guid == LibraryUtils.mainLibrary.guid)
			{
				var libView = LibraryUtils.mainLibrary.createView();
				libView.filterConstraint = LibraryUtils.createConstraint([[ [SBProperties.isList, ["0"]] ],[ [SBProperties.hidden, ["0"]] ]]);
				//Server list
				r.aply.mlcl.mlit[j] = {
					miid: 1,
					mper: 1,
					minm: this._serverName,
					abpl: true,
					mpco: 0,
					meds: 0,
					mimc: libView.length,
				};
				j++;
				end--;
				//Music Playlist
				r.aply.mlcl.mlit[j] = {
					miid: 2,
					mper: 2,
					minm: 'Music',
					aeSP: true,
					mpco: 0,
					aePS: 6,
					meds: 0,
					mimc: libView.length,
				};
				libView = null;
				j++;
				end--;
			}
			
			curMediaList = view.getItemByIndex(i);
			curDaapID = this.getItemDmapID(curMediaList);
			
			r.aply.mlcl.mlit[j] = this.getContainerInfo(curMediaList,metaNameList);
			
			j++;
		}
		this.sendResponse(response,r);
	},
	serveBasePlaylist: function(params,metadata,response)
	{
		r = { apso: {
				mstt: 200,
				muty: 0,
				mtco: 0,
				mrco: 0,
				mlcl: [] }
				};

		this.sendResponse(response,r);				
	},
	servePropertyBrowseFromView: function(property,view,params,metadata,response)
	{
		//ignore lists
		view.filterConstraint = LibraryUtils.createConstraint([[ [SBProperties.isList, ["0"]] ],[ [SBProperties.hidden, ["0"]] ]]);
		
		//for browse its called filter not query!
		params['filter'] = params['query'];
		
		//apply filters
		this.applyItemsFilters(view,params);
		
		var start = 0;
		var end = Number.MAX_VALUE;
		if (typeof(params.index) != 'undefined')
		{
			var iDelimiter = params['index'].indexOf('-');
			start = parseInt(params['index'].substr(0,iDelimiter));
			end = parseInt(params['index'].substr(iDelimiter+1)) + 1;
		}
		r = { abro: {
				mstt: 200,
				muty: 0,
				mtco: 0,
				mrco: 0,
				},
			};
		
		var resultListCode = 'mlcl';
		if (property == SBProperties.artistName)
			resultListCode = 'abar';
		else if (property == SBProperties.albumName)
			resultListCode = 'abal';
		else if (property == SBProperties.genre)
			resultListCode = 'abgn';
		else if (property == SBProperties.composerName)
			resultListCode = 'abcp';
		else
			throw HTTPUtils.HTTP_400;
			
		var results = [];
		var pIndex = view.cascadeFilterSet.appendFilter(property);
		var valuesEnumerator = view.getDistinctValuesForProperty(property);
		var countAvailable = 0;
		var countIncluded = 0;
		while (valuesEnumerator.hasMore())
		{
			countAvailable++;
			var value = valuesEnumerator.getNext();
			if (countAvailable > start && countAvailable <= end)
			{					
				results.push(value);
				countIncluded++;
			}
		}
		r.abro[resultListCode] = { mlit: results };
		r.abro['mtco'] = countAvailable;
		r.abro['mrco'] = countIncluded;

		this.sendResponse(response,r);
	},
	handleGroupsFromView: function(property,view,path,params,metadata,response)
	{
		//ignore lists
		view.filterConstraint = LibraryUtils.createConstraint([[ [SBProperties.isList, ["0"]] ],[ [SBProperties.hidden, ["0"]] ]]);
		
		//apply filters
		this.applyItemsFilters(view,params);
		//done with query param
		params['query'] = undefined;
		
		
		if (path.length < 1)
			//Serve up the group list
			return this.serveGroupsFromView(property,view,params,metadata,response);
		else
		{	//looking for a subset of the group
			var index = parseInt(path.shift())-1;	//comes in a 1-based
			dumpn('Group Index: ' + index);
			
			var valuesEnumerator = view.getDistinctValuesForProperty(property);
			var i=0;
			var propVal = '';
			while (valuesEnumerator.hasMore())
			{
				propVal = valuesEnumerator.getNext();
				if (i==index) break;
				i++;
			}
			//filter by this property
			var pIndex = view.cascadeFilterSet.appendFilter(property);
			view.cascadeFilterSet.set(pIndex,[propVal],1);
			
			dumpn('View length: ' + view.length);
			return this.handleGroup(view,property,path,params,metadata,response);
		}
	},
	handleGroup: function(view,property,path,params,metadata,response)
	{
		//need something else
		if (path.length < 1)
			throw HTTPUtils.HTTP_401;
			
		//for metadata handle as item
		if(path[0] == 'extra_data')
			return this.handleMediaItem(view.getItemByIndex(0),path,params,metadata,response);
		
		//otherwise handle as view
		return this.handleView(view,path,params,metadata,response);	
	},
	serveGroupsFromView: function(property,view,params,metadata,response)
	{
		var start = 0;
		var end = Number.MAX_VALUE;
		if (typeof(params.index) != 'undefined')
		{
			var iDelimiter = params['index'].indexOf('-');
			start = parseInt(params['index'].substr(0,iDelimiter));
			end = parseInt(params['index'].substr(iDelimiter+1))+1;
		}
		
		//list of request metadata
		var metaStr = (typeof(params['meta']) == 'undefined') ? '' : params['meta'];
		var metaNameList = metaStr.split(',');
		
		var resultListCode = 'abro';
		if (property == SBProperties.artistName)
			resultListCode = 'agar';
		else if (property == SBProperties.albumName)
			resultListCode = 'agal';
		else if (property == SBProperties.genre)
			resultListCode = 'aggn';
		else if (property == SBProperties.composerName)
			resultListCode = 'agcp';
		else
			throw HTTPUtils.HTTP_400;
			
		resultList = {
				mstt: 200,
				muty: 0,
			};
		
		var results = [];
		dumpn('View length: ' + view.length);
		var valuesEnumerator = view.getDistinctValuesForProperty(property);
		var pIndex = view.cascadeFilterSet.appendFilter(property);
		var countAvailable = 0;
		var countIncluded = 0;
		while (valuesEnumerator.hasMore())
		{
			countAvailable++;
			var value = valuesEnumerator.getNext();
			if (countAvailable > start && countAvailable <= end)
			{
				view.cascadeFilterSet.set(pIndex,[value],1);
				var itemCount = view.length;
				var item = view.getItemByIndex(0);
				var dmapID = this.getItemDmapID(item);
				var dmapObj = {
						minm: value,
						mimc: itemCount,
						miid: dmapID,
						mper: dmapID,
					};
					
				//get additional request metadata
				for (var metaName in metaNameList)
				{
					switch (metaName)
					{
						case 'daap.songartist':
							dmapObj['asar'] = item.getProperty(SBProperties.artistName);
							break;
					}
				}
				results.push( dmapObj );
				countIncluded++;
			}
		}
		resultList['mtco'] = countAvailable;
		resultList['mrco'] = countIncluded;
		resultList['mlcl'] = { mlit: results };
		dmapRootObj = {};
		dmapRootObj[resultListCode] = resultList;
		
		this.sendResponse(response,dmapRootObj);
	},
	getItemInfo: function(aMediaItem,metaNameList)
	{
			var trackName = aMediaItem.getProperty(SBProperties.trackName);
			var albumName = aMediaItem.getProperty(SBProperties.albumName);
			var artistName = aMediaItem.getProperty(SBProperties.artistName);
			var genre = aMediaItem.getProperty(SBProperties.genre);
			var dmapID = this.getItemDmapID(aMediaItem);
			var contentLength = aMediaItem.contentLength;
			var contentType = aMediaItem.contentSrc.QueryInterface(Ci.nsIURL).fileExtension;
			var duration = aMediaItem.getProperty(SBProperties.duration);
			
			if (!trackName) trackName = '';
			if (!albumName) albumName = '';
			if (!artistName) artistName = '';
			if (!duration) duration = 0; else duration = duration / 1000;
			if (!genre) genre = '';
			if (!contentLength) contentLength = 0;
			if (!contentType) contentType = '';
			
			var retVal = {
					mikd: 2,
					miid: dmapID,
					mper: dmapID,
					minm: trackName,
					asal: albumName,
					asar: artistName,
					asgn: genre,
					assz: contentLength,
					asfm: contentType,
					astm: duration,
					};
			
		//other optional metadata
		for(i in metaNameList)
		{
			switch(metaNameList[i])
			{
				case 'daap.songalbumid':
					retVal['asai'] = dmapID;
					break;
				case 'com.apple.itunes.has-video':
					retVal['aeHV'] = 0;
					break;
				case 'dmap.containeritemid':
					retVal['mcti'] = dmapID;
					break;
				case 'com.apple.itunes.jukebox-client-vote':
					retVal['ceJV'] = 0;
					break;
			}
		}
		return retVal;
	},
	getContainerInfo: function(aMediaList,metaNameList)
	{
		var dmapID = this.getItemDmapID(aMediaList);
		var isSpecial = (aMediaList.type == 'smart');
		var retVal = {
					miid: dmapID,
					mper: dmapID,
					minm: aMediaList.name,
					mimc: aMediaList.length,
					};
		
		if (isSpecial)
			retVal['aeSP'] = true;
			
		//other optional metadata
		//for(metaName in metaNameList)
		//{
		//}
		return retVal;
	},
	/*
	 * parses filter parameters and applies it to a view
	 */
	applyItemsFilters: function(view,params)
	{
		//NOTE: Almost all of this is pointless, in Songbird any search searches all fields automatically
		// and filters need an exact match
		// Shouldn't cause us a problem with iTunes though
		filters = this.parseFilters(params);
		
		if (filters == undefined)
			return;

		//time to work on special cases!
		for (propIndex in filters)
		{
			if (filters[propIndex].property == 'daap.songalbumid')
			{	//Special case, we need to get the album and artist name and filter for those
				albumIDfilter = filters.splice(propIndex,1)[0];
				var dmapIDStr = albumIDfilter.value;
				
				//a special case of a special case, songalbumid=0 is passed in login sequence
				//dont know what this means, itunes sends back no answers
				
				var libraryView = LibraryUtils.mainLibrary.createView();
				libraryView.filterConstraint = LibraryUtils.createConstraint([[ [this.dmapIDProperty, [dmapIDStr]] ]]);
				if (libraryView.length > 0)
				{
					var mediaItem = libraryView.getItemByIndex(0);
					var albumName = mediaItem.getProperty(SBProperties.albumName);
					var filterIndex = view.cascadeFilterSet.appendFilter(SBProperties.albumName);
					view.cascadeFilterSet.set(filterIndex,[albumName],1);
				}
				else
				{
					//a filter that will never match anything?
					var filterIndex = view.cascadeFilterSet.appendFilter(SBProperties.albumName);
					view.cascadeFilterSet.set(filterIndex,['asdfghjkl'],1);
				}
			}
		}
		
		//TODO: this currently does: search all fields for the first value in the list
		if (filters.length > 0)
		{
			//HACK! If only 1 property, we are going to filter instead of search
			if (filters.length == 1 && !filters[0].isSearch)
			{
				var filterIndex = view.cascadeFilterSet.appendFilter(filters[0].property)
				view.cascadeFilterSet.set(filterIndex,[ filters[0].value ],1);
			}
			else
			{
				var properties = [];
				for (propIndex in filters)
					properties.push(filters[propIndex].property);
				
				var filterIndex = view.cascadeFilterSet.appendSearch(properties, properties.length)
				view.cascadeFilterSet.set(filterIndex,[ filters[0].value ],1);
			}
		}
	},
	/*
	 * parses a filter string
	 */
	parseFilters: function(params)
	{
		//decode query
		if (params['query'] == undefined)
			return undefined;
		
		var filters = [];
		var operator = undefined;
		q = params.query;
		if (q[0] == "'")
		{
			var filter = this.parseFilter(q)
			if (filter)
				filters.push(filter);
		}
		else if(q[0] == '(')
		{
			//trim brackets
			q = q.substr(1,q.length-2);
			//read segments
			while (q.length > 0)
			{
				if (q[0] == '(')
				{	//ignore nested brackets
					q = q.substr(q.indexOf(")")+1 );
					
					if (q.length > 0)
					{
						operator = q[0];
						q = q.substr(1);
					}
					continue;
				}
				else if(q[0] == '\'')
				{
					var endPos = q.indexOf("'",1)+1
					filterStr = q.substr(0,endPos);
					q = q.substr(endPos);
					
					if (q.length > 0)
					{
						operator = q[0];
						q = q.substr(1);
					}
					
					//we are going to ignore operator and assume it is OR 
					// + = AND
					// , = OR
					var filter = this.parseFilter(filterStr)
					if (filter)
						filters.push(filter);
				}
			}
		}
		return filters	
	},
	parseFilter: function(q)
	{
		var isSearch = false;
		q = q.substring(1,q.length-1);
		parts = q.split(':');
		key = parts[0];
		val = parts[1];
		
		if (key[key.length-1] == '!')
		{	//Cannot handle not equal to, so ignore this filter
			return null;
			//key = key.substr(0,key.length-1);
		}
		if(val == '')
		{
			return null;
		}
		if (val[0] == '*')
		{
			isSearch = true;
			val = val.substr(1);
		}
		if (val[val.length-1] == '*')
		{
			isSearch = true;
			val = val.substr(0,val.length-1);
		}
		
		switch(parts[0])
		{
			case 'dmap.itemname':
				return {property: SBProperties.trackName, value: val, isSearch: isSearch};
			case 'daap.songartist':
				return {property: SBProperties.artistName, value: val, isSearch: isSearch};
			case 'daap.songalbum':
				return {property: SBProperties.albumName, value: val, isSearch: isSearch};
			case 'daap.songalbumid':
				return {property: 'daap.songalbumid', value: val};
		}
	},
	handlePlayer: function(path,params,metadata,response)
	{
		if (path.length < 1 || (comp = path.shift()) == '')
			return this.servePlayers(metadata,params,response);
		
		//check we have authorization for remote control
		//if (!this.checkAuthorizationForRemote(params))
		//		throw HTTPUtils.HTTP_403;
		
		//ignore the control number
		comp = path.shift().toLowerCase();
		
		switch(comp)
		{
			case 'playstatusupdate':
				return this.serveStatusUpdate(metadata,params,response);
			case 'cue':
				return this.handleCue(params,metadata,response);
			case 'getproperty':
				return this.handleGetProperty(params,metadata,response);
			case 'setproperty':
				return this.handleSetProperty(params,metadata,response);
			case 'getspeakers':
				return this.serveSpeakers(params,metadata,response);
			case 'playspec':
				return this.handlePlaySpec(params,metadata,response);
			case 'nowplayingartwork':
				return this.handleNowPlayingArtwork(params,metadata,response);
			case 'items':
				var gMM = Cc["@songbirdnest.com/Songbird/Mediacore/Manager;1"].getService(Ci.sbIMediacoreManager); 
				if (gMM.sequencer && gMM.sequencer.view)
					return this.handleItems(gMM.sequencer.view,path,params,metadata,response);
				else //TODO: return empty list
					return;
				
			case 'nextitem':
			case 'previtem':
			case 'playpause':
			case 'pause':
				return this.handlePlayerMisc(comp,params,metadata,response);
		}
			
	},
	/*
	 * Handles the simple play pause stop next last etc commands
	 */
	handlePlayerMisc: function(comp,params,metadata,response)
	{
		var gMM = Cc["@songbirdnest.com/Songbird/Mediacore/Manager;1"].getService(Ci.sbIMediacoreManager); 
		var sequencer = gMM.sequencer;
		var playback = gMM.playbackControl;
		switch(comp)
		{
			case 'nextitem':
				if (sequencer) sequencer.next();
				break;
			case 'previtem':
				if (sequencer) sequencer.previous();
				break;
			case 'playpause':
				if (playback)
				{
					if (gMM.status.state == gMM.status.STATUS_PLAYING)
						playback.pause();
					else
						playback.play();
				}
				break;
			case 'pause':
				if (playback) playback.pause();
				break;
				
		}
		//update whether or not we changed anything
		this.onMediaCoreChange();
		return;
	},
	/*
	 * handles /ctrl-int with a list of available players
	 */
	servePlayers: function(metadata,params,response)
	{
		r = { caci: {
				mstt: 200,
				mtco: 1,
				mrco: 1,
				mlcl:
					{ mlit: [{
						miid: 1,
						cmik: 1,
						cmsp: 1,
						cmsv: 1,
						cass: 1,
						casu: 1,
						ceSG: 1, }]
					},
				}
			};
			
		return this.sendResponse(response,r);
	},
	/*
	 * checks that remotes pass a session id, and it corresponds to a previous login
	 * that was with a pairing code.
	 */
	checkAuthorizationForRemote: function(params)
	{
		if (typeof(params['session-id']) == undefined) return false;
		for (index in this._currentSessions)
		{
			if (this._currentSessions[index].value == params['session-id'] && this._currentSessions[index].isRemote == true) return true;
		}
			
		return false;
	},
	_waitingStatusRequest: null,
	_mediaCoreListener: {
		that: null,	//set to daapd before listener registered
		onMediacoreEvent: function(ev) {
			switch (ev.type) {
				case Ci.sbIMediacoreEvent.METADATA_CHANGE:
				case Ci.sbIMediacoreEvent.TRACK_CHANGE:
				case Ci.sbIMediacoreEvent.DURATION_CHANGE:
				case Ci.sbIMediacoreEvent.VOLUME_CHANGE:
				case Ci.sbIMediacoreEvent.MUTE_CHANGE:
				case Ci.sbIMediacoreEvent.VIEW_CHANGE:
				case Ci.sbIMediacoreEvent.STREAM_START:
				case Ci.sbIMediacoreEvent.STREAM_PAUSE:
				case Ci.sbIMediacoreEvent.STREAM_STOP:
					this.that.onMediaCoreChange();
				break;
			}
		}
	},
	serveStatusUpdate: function(metadata,params,response)
	{
		if (typeof(params['revision-number']) != undefined && parseInt(params['revision-number']) > this._currentPlayerRevision)
			//They want to wait for a push notification, not supported what to do!?!?
		{
				response.setAsync(true);	//Set to Async so that on return it doesn't end
				this._waitingStatusRequest = null;
				this._waitingStatusRequest = {
					metadata: metadata,
					params: params,
					response: response,
					};
				
				return;
		}
			
		var curItemIndex = -1;
		var curItem = null;
		var position = 0;
		var duration = 1;
		var volume = 50;
		var shuffleMode = 0;
		var repeatMode = 0;
		var curTrackName = '';
		var curTrackArtist = '';
		var curTrackAlbum = '';
		
		var gMM = Cc["@songbirdnest.com/Songbird/Mediacore/Manager;1"].getService(Ci.sbIMediacoreManager); 
		
		curItem = gMM.sequencer.currentItem;
		if (curItem != null)
		{
			curTrackName = curItem.getProperty(SBProperties.trackName);
			if (curTrackName == null) curTrackName = '';
			curTrackArtist = curItem.getProperty(SBProperties.artistName);
			if (curTrackArtist == null) curTrackArtist = '';
			curTrackAlbum = curItem.getProperty(SBProperties.albumName);
			if (curTrackAlbum == null) curTrackAlbum = '';
			curDaapID = this.getItemDmapID(curItem);
		}
		if (gMM.sequencer != null)
		{
			repeatMode = (gMM.sequencer.repeatMode == gMM.sequencer.MODE_REPEAT_NONE) ? 0 :
						 (gMM.sequencer.repeatMode == gMM.sequencer.MODE_REPEAT_ONE)  ? 1 : 
																						2;
							
			shuffleMode =  (gMM.sequencer.mode == gMM.sequencer.MODE_SHUFFLE) ? 1 : 0;
			
		}
		if (gMM.playbackControl != null)
		{
			position = gMM.playbackControl.position;
			duration = gMM.playbackControl.duration;
		}
		if (gMM.volumeControl != null)
		{
			volume = gMM.volumeControl.mute ? 0 : gMM.volumeControl.volume*100;
		}
		var playingStatus = (gMM.status.state == gMM.status.STATUS_PLAYING) ? 4 : 
							(gMM.status.state == gMM.status.STATUS_PAUSED)  ? 3 : 
																			  2; //Stopped
		
		r = { cmst: {
				mstt: 200,
				cmsr: this._currentPlayerRevision + 1,
				caps: playingStatus,
				cash: shuffleMode,
				carp: repeatMode,
				cavc: 1,
				caas: 2,
				caar: 6,
				}
			};
		
		if (curItem != null)
		{	//Only append if required	
			r.cmst.canp= '000000240000184D00001850000002A1';
			r.cmst.cann= curTrackName;
			r.cmst.cana= curTrackArtist;
			r.cmst.canl= curTrackAlbum;
			r.cmst.cang= '';
			r.cmst.asai= curDaapID;
			r.cmst.cmmk= 1;
			r.cmst.cant= duration - position;
			r.cmst.cast= duration;
		}
		
		this.sendResponse(response,r);
		
		//If this was an async response end here
		if (response.isAsync)
			response.end(null);
	},
	onMediaCoreChange: function()
	{
		this._currentPlayerRevision++;
		
		if (this._waitingStatusRequest != null)
		{
			var req = this._waitingStatusRequest;
			this._waitingStatusRequest = null;
			this.serveStatusUpdate(req.metadata,req.params,req.response);
		}
	},
	handleGetProperty: function(params,metadata,response)
	{
		var dmapObj = { cmgt: {
			mstt: 200,
			}
		};
		
		if (params['properties'])
		{
			var props = params['properties'].split(';');
			var gMM = Cc["@songbirdnest.com/Songbird/Mediacore/Manager;1"].getService(Ci.sbIMediacoreManager); 

			for (var i in props)	{
				switch(props[i])	{
					case 'dmcp.volume':
						dmapObj.cmgt['cmvo'] = gMM.volumeControl.mute ? 0 : Math.round(gMM.volumeControl.volume*100);
						break;
				}
			}
		}
		
		this.sendResponse(response,dmapObj);
	},
	handleSetProperty: function(params,metadata,response)
	{
		if(params['dacp.playingtime'])
		{
			playingtime = parseInt(params['dacp.playingtime']);
			var gMM = Cc["@songbirdnest.com/Songbird/Mediacore/Manager;1"].getService(Ci.sbIMediacoreManager); 
			if (gMM.playbackControl) gMM.playbackControl.position = playingtime;
		}
		if(params['dmcp.volume'])
		{
			var volume = parseFloat(params['dmcp.volume']);
			var gMM = Cc["@songbirdnest.com/Songbird/Mediacore/Manager;1"].getService(Ci.sbIMediacoreManager); 
			if (gMM.volumeControl)
			{
				gMM.volumeControl.volume = volume/100;
				if (volume != 0) gMM.volumeControl.mute = false; //make sure not muted
			}
		}
		if(params['dacp.shufflestate'])
		{
			var shuffle = (parseInt(params['dacp.shufflestate']) != 0);
			var gMM = Cc["@songbirdnest.com/Songbird/Mediacore/Manager;1"].getService(Ci.sbIMediacoreManager);
			if (gMM.sequencer) 
				gMM.sequencer.mode = (shuffle) ? gMM.sequencer.MODE_SHUFFLE : gMM.sequencer.MODE_FORWARD;
		}
		if(params['dacp.repeatstate'])
		{
			var repeat = parseInt(params['dacp.repeatstate']);
			var gMM = Cc["@songbirdnest.com/Songbird/Mediacore/Manager;1"].getService(Ci.sbIMediacoreManager);
			if (gMM.sequencer) 
				gMM.sequencer.repeatMode = (repeat == 2) ? gMM.sequencer.MODE_REPEAT_ALL : 
										   (repeat == 1) ? gMM.sequencer.MODE_REPEAT_ONE : 
													       gMM.sequencer.MODE_REPEAT_NONE;
		}	
		this.onMediaCoreChange();
		response.setStatusLine(metadata.httpVersion, 204, "No Content");
	},
	handleCue: function(params,metadata,response)
	{
		var gMM = Cc["@songbirdnest.com/Songbird/Mediacore/Manager;1"].getService(Components.interfaces.sbIMediacoreManager); 
		
		var index = 0;
		if (params.index) {
			try {
				index = parseInt(params.index);
			} catch (e) {
				index = 0; 
			}
		}
		
		if (params['query'])
		{
			var view = LibraryUtils.mainLibrary.createView();
			view.filterConstraint = LibraryUtils.createConstraint([[ [SBProperties.isList, ["0"]] ],[ [SBProperties.hidden, ["0"]] ]]);
			
			dumpn('query: ' + metadata.queryString);
			//apply filters
			this.applyItemsFilters(view,params);
				
			gMM.sequencer.playView(view,index);
		}
		else if (gMM.sequencer.view)
		{
			gMM.sequencer.playView(gMM.sequencer.view,index);
		}
		
		var playback = gMM.playbackControl;
		if (!params.command)
			if (playback) playback.pause();
		else
		{
			switch(params.command)
			{
				case 'pause':
					playback.pause();
			}
		}
			
		//might have properties to set, also sends response
		return this.handleSetProperty(params,metadata,response);
	},
	serveSpeakers: function(params,metadata,response)
	{
		r = { casp: { 
				mstt: 200,
				mdcl: {
					caia: 1,
					minm: 'My Computer',
					msma: 0,
					},
				}
			};
		this.sendResponse(response,r);
	},
	handlePlaySpec: function(params,metadata,response)
	{
	
		var itemStr = params['container-item-spec'];
		var itemID = false;
		var itemIndex = 0;
		var item = null;
		if (itemStr)
		{
			if (itemStr[0] == "'")
				itemStr = itemStr.substr(1);
			if (itemStr[itemStr.length-1] == "'")
				itemStr = itemStr.substr(0,itemStr.length-1);
			parts = itemStr.split(':');
			var itemID = parseInt(parts[1].toUpperCase(),16);
		}
			
		//ignore database spec
		var containerStr = params['container-spec'];
		
		//remove quotes
		if (containerStr[0] == "'")
			containerStr = containerStr.substr(1);
		if (containerStr[containerStr.length-1] == "'")
			containerStr = containerStr.substr(0,containerStr.length-1);
		
		parts = containerStr.split(':');
		var containerID = parseInt(parts[1].toUpperCase(),16);
		
		var view = LibraryUtils.mainLibrary.createView();
		
		//get item to play
		if (itemID)
		{
			view.filterConstraint = LibraryUtils.createConstraint([[ [SBProperties.isList, ["0"]] ],[ [this.dmapIDProperty, [itemID]] ]]);
			if (view.length > 0)
				item = view.getItemByIndex(0);
		}
		
		view.filterConstraint = LibraryUtils.createConstraint([[ [SBProperties.isList, ["1"]] ],[ [this.dmapIDProperty, [containerID]] ]]);
		
		if (view.length < 1 )
			throw HTTPUtils.HTTP_404;
			
		view = view.getItemByIndex(0).createView();
		
		var gMM = Cc["@songbirdnest.com/Songbird/Mediacore/Manager;1"].getService(Components.interfaces.sbIMediacoreManager); 
		
		if (item) {
			try {
				index = view.getIndexForItem(item);
			} catch (e) {
				index = 0; 
			}
		}
			
		gMM.sequencer.playView(view,index);
		
		//might have properties to set
		this.handleSetProperty(params,metadata,response);
	},
	handleNowPlayingArtwork: function(params,metadata,response)
	{
		//TODO: handle now playing artwork!
		var gMM = Cc["@songbirdnest.com/Songbird/Mediacore/Manager;1"].getService(Ci.sbIMediacoreManager); 
		var curItem = (gMM.sequencer) ? gMM.sequencer.currentItem : null;
		
		if (curItem == null)
		{
			response.setStatusLine(metadata.httpVersion, 204, "No Content");
			return;
		}
		
		return this.serveMediaItemArtwork(curItem,params,metadata,response);	
			
	},
	sendResponse: function(response, r)
	{
		//default to x-dmap-tagged
		response.setHeader("Content-Type","application/x-dmap-tagged",false);
		response.setHeader("DAAP-Server","Songbird Telescope 0.9",false);
		output = DMAP.stringify(r);
		
		// dump(DMAP.print(r));
		new this.BinaryOutputStream(response.bodyOutputStream).writeByteArray(output,output.length);
	},
	 /*
	 * serves a media Item
	 */
	handleMediaItem: function(item,path,params,metadata,response)
	{
		if (path.length > 0)
		{
			var comp = path.shift().toLowerCase();
			switch (comp)
			{
				case 'extra_data':
					comp = path.shift().toLowerCase();
					switch (comp)
					{
						case 'artwork':
							return this.serveMediaItemArtwork(item,params,metadata,response);
					}
					break;
			}
		}
		else
			return this.serveMediaItem(item,metadata,response);
	},
	/*
	 * sends a raw file stream of a given media item
	 */
	serveMediaItem: function(item,metadata,response)
	{
		var ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
		// try
		// {
		var url = item.contentSrc.QueryInterface(Ci.nsIURL);
		// } catch (e) { throw HTTPUtils.HTTP_404; }
		var type =  Cc["@mozilla.org/uriloader/external-helper-app-service;1"]
				   .getService(Ci.nsIMIMEService)
				   .getTypeFromExtension(url.fileExtension);
		
		try 
		{
			var channel = ios.newChannelFromURI(item.contentSrc);
			var is = channel.open();
			response.setHeader("Content-Type", type, false);
			response.bodyOutputStream.writeFrom(is, is.available());
			is.close();  
		}
		catch(e)
		{
			throw HTTPUtils.HTTP_404;
		}
		return;
	},
	/*
	 * sends the image associated with a given media Item
	 */
	serveMediaItemArtwork: function(item,params,metadata,response)
	{
		//TODO: we should resize these images based on mw= and mh= params
		imgTools = Components.classes["@mozilla.org/image/tools;1"].createInstance(Components.interfaces.imgITools);

 
		var albumArtURL = item.getProperty(SBProperties.primaryImageURL);
		if (albumArtURL == null)
		{
			response.setStatusLine(metadata.httpVersion, 204, "No Content");
			return;
		}
		
		var ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
		var uri = ios.newURI(albumArtURL,null,null);
		var url = uri.QueryInterface(Components.interfaces.nsIURL);

		dump('Album art extension: ' + url.fileExtension + "\n");
		var type =  Cc["@mozilla.org/uriloader/external-helper-app-service;1"]
				   .getService(Ci.nsIMIMEService)
				   .getTypeFromExtension(url.fileExtension);
		
		try {
			var channel = ios.newChannelFromURI(url);
			var is = channel.open();
			
			//if (params['mw'] != undefined || params['mh'] != undefined)
			//{
			//	//resize image
			//	var outParam = { value: null };
			//	imgTools.decodeImageData(is, type, outParam);
			//	is.close();
			//	var img = outParam.value;
			//	var scaleFactorW = 1;
			//	var scaleFactorH = 1;
			//	if (params['mw'] != undefined) scaleFactor = parseInt(params['mw'])/img.width;
			//	if (params['mh'] != undefined) scaleFactor = parseInt(params['mh'])/img.height;
			//	
			//	var scaleFactor = (scaleFactorW > scaleFactorH) ? scaleFactorH : scaleFactorW;
			//	var newW = Math.round(img.width*scaleFactor);
			//	var newH = Math.round(img.height*scaleFactor);
			//	
			//	var is = imgTools.encodeScaledImage(img, type, newW, newH);
			//}
				
			response.setHeader("Content-Type", type, false);
			response.bodyOutputStream.writeFrom(is, is.available());
			is.close();  
		}
		catch(e)
		{
			response.setStatusLine(metadata.httpVersion, 204, "No Content");
		}
		
	},
    /*
	 * checks HTTP basic authentication against password
	 */
	checkHTTPHeaderAuthorization: function(metadata)
	{
		if (this._password == '')
			return true;
			
		try
		{
			var auth = metadata.getHeader("Authorization");
		}
		catch (e)
		{
			return false; 
		}
		var authorizationDecoded = Base64.decode(auth.replace(/Basic /, ""));
		var authorizationData = authorizationDecoded.split(":");
		
		if ((authorizationData[1] == this._password)) 
			return true;
		
		return false;
	},
	/*
	 * serves a HTTP 401 unauthorized request
	 */
	serveAuthorizationRequest: function(metadata,response)
	{
		response.setHeader("WWW-Authenticate", "Basic realm=\"Songbird DAAP\"", false);
		response.setStatusLine(metadata.httpVersion, 401, "Unauthorized");
		return;
	},
	get wrappedJSObject()
	{
		return this;
	},
    /**
	 * Callback saying we have registered with the DNS SD service
	 */
	onRegister: function( service, add, error, serviceName, regType, domain)
    {
  },
    /**
   * Gets the server for this instance
   **/
    get server()
    {
		if (this._server == null)
		{
			this._server = Cc['@awilco.net/jshttp;1'].createInstance(Components.interfaces.awIHttpServer);
			var port = Preferences.get('extensions.wings.port');
            this._server.start(port);
		}
		return this._server;
	},
	/*
	 *calls passed function in the context of this instance
	 */
	callInContext: function(fn) {
		var context = this;
		return function() { fn.apply(context, arguments); }
	},
    /**
   * Restarts the server if it crashes
   **/
	// checkServerIsRunning: function()
	// {
		// //check we are still running
		// try
		// {
			// var telescope = Components.classes["@awilco.net/telescope;1"].getService(Components.interfaces.nsISupports);
			// telescope = telescope.wrappedJSObject;
		
				
			// if (telescope.server && telescope.server.isStopped())
			// {
				// dump('>>> Telescope HTTP Server stopped, attempting restart...\n');
				// var port = Preferences.get('extensions.telescope.port');
				// telescope.server.start(port);
			// }
		// }
		// catch(e)
		// {
			// dump(e);
		// }
	// },
	/*
	 * Gets a new DMAP ID
	 */
	_getNewDmapID: function()
	{
		this._lastDmapID++;
		Preferences.set('extensions.telescope.dmapid',this._lastDmapID.toString());
		return this._lastDmapID;
	},
	/*
	 * Gets an items DMAP ID, creates if does not exist
	 */
	getItemDmapID: function(aMediaItem)
	{
		var idStr = aMediaItem.getProperty(this.dmapIDProperty);
		
		if (idStr != null && idStr != 'NaN')
		{
			var id = parseInt(idStr);
			return id;
		}
		else
		{
			id = this._getNewDmapID();
			aMediaItem.setProperty(this.dmapIDProperty,id.toString());
			return id;
		}
	 },
	_browserInstances: [],
	_remotes: {},
	browseForRemotes: function(listener)
	{
		
		var browseContext = {
			_daapd: null,
			_listener: null,
			_dnssdService: null,
			_dnssdBrowseInstance: null,
			_resolver: null,
			_resolveTimer: -1,
			_resolved: false,
			_serviceInfo: {},
			startBrowsing: function(owner, listener)
			{
				this._daapd = owner;
				this._listener = listener;
				this._dnssdService = Components.classes["@bonjourfoxy.net/BFDNSSDService;1"]
                                 .createInstance(Components.interfaces.IBFDNSSDService);
				
				this._dnssdBrowseInstance = this._dnssdService.browse(0, '_touch-remote._tcp', '', this.callInContext(this.browseListener));
			},
			browseListener: function(service, add, interfaceIndex, error, serviceName, regtype, domain)
			{
				if (error)
					return;
				
				if (!add)
				{
					this._listener.onRemoteLost(serviceName);
					return;
				}
				
				this._serviceInfo = {
					'serviceName': serviceName,
					'interfaceIndex': interfaceIndex,
					'regtype': regtype,
					'domain': domain,
					};
					
				try { this._resolver.stop(); } catch(e) {}
				this._resolver = this._dnssdService.resolve(interfaceIndex,serviceName,regtype, domain,this.callInContext(this.resolveListener));
				
				try { this.clearTimeout(this._resolverTimer); } catch (e) {}
				this._resolverTimer = this.setInterval(function(){
					if (this._resolved) return;
					 try {this._resolver.stop(); } catch(e) {}
				}, 30000);
			},
			resolveListener: function(service, interfaceIndex, error, fqdn, hostname, port, txtRecords) 
			{
				if (error)
					return;
				
				var txtRec;
				for (i=0;i<txtRecords.length;i++)
				{
					txtRec = txtRecords.queryElementAt(i,Ci.nsIVariant).toString();
					kv = txtRec.split('=');
					switch(kv[0])
					{
						case 'DvNm':
							
							this._serviceInfo['deviceName'] = kv[1];
							break;
						case 'Pair':
							this._serviceInfo['pairingHash'] = kv[1];
							break;	
					}
				}
				
				this._serviceInfo['host'] = hostname;
				this._serviceInfo['port'] = port;
				try { this._resolver.stop(); } catch(e) {}
				// this._resolver = this._dnssdService.resolve(this._serviceInfo.interfaceIndex, this._serviceInfo.serviceName, this._serviceInfo.regtype, this._serviceInfo.domain, "Pair", this.callInContext(this.txtPairingListener));
				
				// try { this.clearTimeout(this._resolverTimer); } catch (e) {}
				// this._resolverTimer = this.setInterval(function(){
					// if (this._resolved) return;
					 // try {this._resolver.stop(); } catch(e) {}
				// }, 30000);
				
			// },
			// txtPairingListener: function(service, interfaceIndex, error, fqdn, hostname, port, key, value)
			// {
				// if (error)
					// return;
				
				// this._serviceInfo['pairingHash'] = value;
				
				this._daapd.remoteFound(this._serviceInfo.serviceName,
					this._serviceInfo.deviceName,
					this._serviceInfo.host,
					this._serviceInfo.port,
					this._serviceInfo.pairingHash, 
					this._listener);
					
				this._listener.onRemoteFound(this._serviceInfo.serviceName,this._serviceInfo.deviceName);
			},
			setInterval: function (func, delay)
			{
				var timer =
				Components.classes["@mozilla.org/timer;1"].createInstance(Components.interfaces.nsITimer);
				var callback = { that: this, notify: function () { func.call(this.that); } };
				timer.initWithCallback(callback, delay,
				Components.interfaces.nsITimer.TYPE_REPEATING_SLACK);
				return timer;
			},
			clearTimeout: function (timer)
			{
				timer.cancel();
			},
			callInContext: function(fn) {
				var context = this;
				return function() { fn.apply(context, arguments); }
			},
			stop: function()
			{
				try {this._resolver.stop(); } catch(e) {}
				try {this._dnssdBrowseInstance.stop(); } catch(e) {}
			},
				
		};
		
		var id = this._browserInstances.push(browseContext);
		browseContext.startBrowsing(this,listener);
		return id;
	},
	stop: function(id)
	{
		try {this._browserInstances[id].stop(); } catch(e) {}
	},
	remoteFound: function(remoteID, name, host, port, pairingCode, listener)
	{
		this._remotes[remoteID] = {
			name: name,
			host: host,
			port: port,
			pairingCode: pairingCode,
			listener: listener,
			paired: false,
			};
	},
	pairRemote: function(remoteID, pairingCode)
	{
		var remote = this._remotes[remoteID];
		if (!remote) return;
		
		var code = remote.pairingCode.toUpperCase() + pairingCode.substr(0,1) + '\0' + pairingCode.substr(1,1) + '\0' + pairingCode.substr(2,1) + '\0' + pairingCode.substr(3,1) + '\0';
		
		var hash = MD5.hex_md5(code).toUpperCase();
		var url = 'http://' + remote.host + ':' + remote.port + '/pair?pairingcode=' + hash + '&servicename=' + this._ID;
				
		var channel = this._ioService.newChannel(url,null,null);
		channel = channel.QueryInterface(Ci.nsIHttpChannel);
		var stream = channel.open();

		var bis =  Cc["@mozilla.org/binaryinputstream;1"].createInstance(Ci.nsIBinaryInputStream);
		bis.setInputStream(stream);
		var data = [];
		do {
			var count = stream.available();
			var buffer = bis.readByteArray(count);
			data = data.concat(buffer);	
		}while (count > 0);
		
		 
		for (i=0; i<data.length;i++)
		{
			var str = (data[i] == '\0') ? '-' : String.fromCharCode(data[i]);
		}
		var dmapObj = DMAP.parse(data);
		if (channel.responseStatus == 200)
		{
			this._remotes[remoteID].paired = true;
			this.addRemoteGuid('0x' + dmapObj.cmpa.cmpg.toUpperCase().toString());
			remote.listener.onRemotePaired(remoteID);
		}
	},
	addRemoteGuid: function(guid)
	{
		this._knownRemotes.push(guid);
		var remoteStr = this._knownRemotes.join(';');
		Preferences.set('extensions.telescope.knownRemotes',remoteStr);
	},
	generateUniqueHex: function(length)
	{
		var str = '';
		do
		{
			var chunklength = (length > 16) ? 16 : length;
			var dateObj = new Date();
			var seed = dateObj.getTime();
			var hex = MD5.hex_md5(seed);
			str += hex.substr(0,chunklength);
			length -= chunklength;
		} while (length > 0)
		return str;
	},
    _contentCodes: 
    {
		mstt: { type: 'int',	name: 'dmap.status' },
		miid: { type: 'int',	name: 'dmap.itemid' },
		minm: { type: 'string',	name: 'dmap.itemname' },
		mikd: { type: 'byte',	name: 'dmap.itemkind' },
		mper: { type: 'long',	name: 'dmap.persistentid' },
		mcon: { type: 'list',	name: 'dmap.container' },
		mcti: { type: 'int',	name: 'dmap.containeritemid' },
		mpco: { type: 'int',	name: 'dmap.parentcontainerid' },
		msts: { type: 'string',	name: 'dmap.statusstring' },
		mimc: { type: 'int',	name: 'dmap.itemcount' },
		mctc: { type: 'int',	name: 'dmap.containercount' },
		mrco: { type: 'int',	name: 'dmap.returnedcount' },
		mtco: { type: 'int',	name: 'dmap.specifiedtotalcount' },
		mlcl: { type: 'list',	name: 'dmap.listing', isArray: true },
		mlit: { type: 'list',	name: 'dmap.listingitem' },
		mbcl: { type: 'list',	name: 'dmap.bag' },
		mdcl: { type: 'list',	name: 'dmap.dictionary', isArray: true },
		msrv: { type: 'list',	name: 'dmap.serverinforesponse' },
		msau: { type: 'byte',	name: 'dmap.authenticationmethod' },
		mslr: { type: 'byte',	name: 'dmap.loginrequired' },
		mpro: { type: 'version',name: 'dmap.protocolversion' },
		apro: { type: 'version',name: 'dmap.protocolversion' },
		msal: { type: 'byte',	name: 'dmap.supportsautologout' },
		msas: { type: 'byte',	name: 'dmap.authenticationschemes' },
		msup: { type: 'byte',	name: 'dmap.supoortsupdate' },
		mspi: { type: 'byte',	name: 'dmap.supportspersistentids' },
		msex: { type: 'byte',	name: 'dmap.supportsextensions' },
		msbr: { type: 'byte',	name: 'dmap.supportsbrowse' },
		msqy: { type: 'byte',	name: 'dmap.supportsquery' },
		msix: { type: 'byte',	name: 'dmap.supportsindex' },
		msrs: { type: 'byte',	name: 'dmap.supportsresolve' },
		mstm: { type: 'int',	name: 'dmap.timeoutinterval' },
		msdc: { type: 'int',	name: 'dmap.databasecount' },
		mstc: { type: 'date',	name: 'dmap.utctime' },
		msto: { type: 'int',	name: 'dmap.utcoffset' }, 
		mccr: { type: 'list',	name: 'dmap.contentcodesresponse' },
		mcnm: { type: 'int',	name: 'dmap.contentcodesnumber' },
		mcna: { type: 'string',	name: 'dmap.contentcodesname' },
		mcty: { type: 'short',	name: 'dmap.contentcodestype' },
		mlog: { type: 'list',	name: 'dmap.loginresponse' },
		mlid: { type: 'int',	name: 'dmap.sessionid' },
		mupd: { type: 'list',	name: 'dmap.updateresponse' },
		msur: { type: 'int',	name: 'dmap.serverrevision' },
		muty: { type: 'byte',	name: 'dmap.updatetype' },
		mudl: { type: 'list',	name: 'dmap.deletedidlisting' },
		avdb: { type: 'list',	name: 'dmap.serverdatabases' },
		abro: { type: 'list',	name: 'dmap.databasebrowse' },
		abal: { type: 'list',	name: 'dmap.browsealbumlisting', isArray: true },
		abar: { type: 'list',	name: 'dmap.browseartistlisting', isArray: true },
		abcp: { type: 'list',	name: 'dmap.browsecomposerlisting', isArray: true },
		abgn: { type: 'list',	name: 'dmap.browsegenrelisting', isArray: true },
		agal: { type: 'list',	name: 'dmap.groupalbum' },
		adbs: { type: 'list',	name: 'dmap.databasesongs' },
		asal: { type: 'string',	name: 'daap.songalbum' },
		asar: { type: 'string',	name: 'daap.songartist' },
		asbt: { type: 'short',	name: 'daap.songbeatsperminute' },
		asbr: { type: 'short',	name: 'daap.songbitrate' },
		ascm: { type: 'string',	name: 'daap.songcomment' },
		asco: { type: 'byte',	name: 'daap.songcompilation' },
		asda: { type: 'date',	name: 'daap.songdateadded' },
		asdm: { type: 'date',	name: 'daap.songdatemodified' },
		asdc: { type: 'short',	name: 'daap.songdisccount' },
		asdn: { type: 'short',	name: 'daap.songdiscnumber' },
		asdb: { type: 'byte',	name: 'daap.songdisabled' },
		aseq: { type: 'string',	name: 'daap.songeqpreset' },
		asfm: { type: 'string',	name: 'daap.songformat' },
		asgn: { type: 'string',	name: 'daap.songgenre' },
		asdt: { type: 'string',	name: 'daap.songdescription' },
		asrv: { type: 'byte',	name: 'daap.songrelativevolume' },
		assr: { type: 'int',	name: 'daap.songsamplerate' },
		assz: { type: 'int',	name: 'daap.songsize' },
		asst: { type: 'int',	name: 'daap.songstarttime' },
		assp: { type: 'int',	name: 'daap.songstoptime' },
		astm: { type: 'int',	name: 'daap.songtime' },
		astc: { type: 'short',	name: 'daap.songtrackcount' },
		astn: { type: 'short',	name: 'daap.songtracknumber' },
		asur: { type: 'byte',	name: 'daap.songuserrating' },
		asyr: { type: 'short',	name: 'daap.songyear' },
		asdk: { type: 'byte',	name: 'daap.songdatakind' },
		asul: { type: 'string',	name: 'daap.songdataurl' },
		aply: { type: 'list',	name: 'daap.databaseplaylists' },
		abpl: { type: 'byte',	name: 'daap.baseplaylist' },
		apso: { type: 'list',	name: 'daap.playlistsongs' },
		prsv: { type: 'list',	name: 'dmap.resolve' },
		arif: { type: 'list',	name: 'dmap.resolveinfo' },
		ated: { type: 'short',  name: 'daap.ated' },
		
		cmst: { type: 'list',	name: 'dacp.status' },
		cmpa: { type: 'list',   name: 'dacp.pairinganswer' }, 
		cmpg: { type: 'hex',    name: 'dacp.pairingguid' },
		cmnm: { type: 'string', name: 'dacp.devicename' },
		cmty: { type: 'string', name: 'dacp.devicetype' },
		cmsr: { type: 'int',	name: 'dacp.serverrevision' },
		caps: { type: 'byte',	name: 'dacp.playstatus' },
		cash: { type: 'byte',	name: 'dacp.shufflestate' },
		carp: { type: 'byte',	name: 'dacp.repeatstate' },
		cavc: { type: 'byte',	name: 'dacp.unknown' },
		caas: { type: 'int',	name: 'dacp.unknown' },
		caar: { type: 'int',	name: 'dacp.unknown' },
		canp: { type: 'hex',	name: 'dacp.nowplayingguid' },
		cann: { type: 'string',	name: 'dacp.nowplayingname' },
		cana: { type: 'string',	name: 'dacp.nowplayingartist' },
		canl: { type: 'string',	name: 'dacp.nowplayingalbum' },
		cang: { type: 'string',	name: 'dacp.unknown' },
		asai: { type: 'long',	name: 'daap.songalbumid' },
		cmmk: { type: 'int',	name: 'dacp.unknown' },
		cant: { type: 'int',	name: 'dacp.remaininglength' },
		cast: { type: 'int',	name: 'dacp.totalength' },
		cmvo: { type: 'int',	name: 'dmcp.volume' },
		caci: { type: 'list',   name: 'dacp.controllers' },
		cmik: { type: 'byte',   name: 'dacp.cmik' },
		cmsp: { type: 'byte',   name: 'dacp.supportsplayback' },  /* guess !*/
		cmsv: { type: 'byte',   name: 'dacp.supportsvolume' },    /* guess !*/
		cass: { type: 'byte',   name: 'dacp.surroundsound' },     /* guess! */
		casu: { type: 'byte',   name: 'dacp.casu' },
		ceSG: { type: 'byte',   name: 'dacp.ceSG' },
		casp: { type: 'list',   name: 'dacp.speakers' },
		caia: { type: 'byte',   name: 'dacp.isactive' },
		msml: { type: 'list',   name: 'dmap.msml' },
		msma: { type: 'long',   name: 'dmap.msma' },
		cmgt: { type: 'list',   name: 'dmcp.getproperty' },
		aeSV: { type: 'version',name: 'com.apple.itunes.music-sharing-version' },
		aeFP: { type: 'byte',	name: 'aeFP' },
		msed: { type: 'byte',	name: 'dmap.editable' },		
		aeNV: { type: 'int',	name: 'com.apple.itunes.norm-volume' },
		aeSP: { type: 'byte',	name: 'com.apple.itunes.smart-playlist' },
		aeHV: { type: 'byte',   name: 'com.apple.itunes.has-video' },
		asaa: { type: 'string', name: 'daap.songalbumartist' },
		aeGG: { type: 'int',    name: 'com.apple.itunes.gapless-enc-dur' },
		aeGU: { type: 'long',   name: 'com.apple.itunes.gapless-dur' },
		aeGR: { type: 'long',   name: 'com.apple.itunes.gapless-resy' },
		aeGE: { type: 'int',    name: 'com.apple.itunes.gapless-enc-del' },
		asgp: { type: 'byte',   name: 'daap.songgapless' },
		aePS: { type: 'byte',	name: 'com.apple.itunes.special-playlist' },
		ased: { type: 'short',  name: 'daap.songextradata' },
		asdr: { type: 'date',   name: 'daap.songdatereleased' },
		asdp: { type: 'date',   name: 'daap.songdatepurchased' },
		ashp: { type: 'byte',   name: 'daap.songhasbeenplayed' },
		assn: { type: 'string', name: 'daap.sortname' },
		assa: { type: 'string', name: 'daap.sortartist' },
		assl: { type: 'string', name: 'daap.sortalbumartist' },
		assu: { type: 'string', name: 'daap.sortalbum' },
		assc: { type: 'string', name: 'daap.sortcomposer' },
		asss: { type: 'string', name: 'daap.sortseriesname' },
		asbk: { type: 'byte',   name: 'daap.bookmarkable' },
		asbo: { type: 'int',    name: 'daap.songbookmark' },
		aspu: { type: 'string', name: 'daap.songpodcasturl' },
		aeCR: { type: 'string', name: 'com.apple.itunes.content-rating' },
		asls: { type: 'long',   name: 'daap.songlongsize' },
		aeSG: { type: 'byte',   name: 'com.apple.itunes.saved-genius' },
		meds: { type: 'int',    name: 'dmap.editcommandssupported' },
		aeHD: { type: 'byte',   name: 'com.apple.itunes.is-hd-video' },
		ceJV: { type: 'int',    name: 'com.apple.itunes.jukebox-vote' },
		ceJC: { type: 'byte',   name: 'com.apple.itunes.jukebox-client-vote' },
		ceJI: { type: 'int',    name: 'com.apple.itunes.jukebox-current' },
		ceJS: { type: 'short',  name: 'com.apple.itunes.jukebox-score' },
		aeSE: { type: 'long',   name: 'com.apple.itunes.store-pers-id' },
		aeXD: { type: 'string', name: 'com.apple.itunes.xid' },
		aeMK: { type: 'int',    name: 'com.apple.itunes.extended-media-kind' },
		ceWM: { type: 'null',   name: 'ceWM' },
		ceVO: { type: 'byte',   name: 'ceVO' },
	},
    /**
   * Generates a query interface, include all components this class will
   * implement except nsISupports which is included by default.
   */
    QueryInterface: XPCOMUtils.generateQI(daapd_supported_interfaces)

};

/**
 * NSGetModule is called to generate the module, pass all classes that need to
 * be implemented.
 */
function NSGetModule(compMgr, fileSpec) {
  return XPCOMUtils.generateModule([daapd]);
}